package com.wanlian.net.core;

import com.wanlian.File.NetFileInfo;
import com.wanlian.json.JsonStringUtil;
import com.wanlian.net.File.DeviceClientFileHandler;
import com.wanlian.netty.File.msg.FilePeerMsg;
import com.wanlian.netty.File.msg.FilePeerMsgDecoder;
import com.wanlian.netty.File.msg.FilePeerMsgEncoder;
import com.wanlian.netty.File.model.FileHead;
import com.wanlian.netty.File.msg.down.FilePeerDownHeadMsg;
import com.wanlian.netty.File.msg.down.FilePeerDownMsg;
import com.wanlian.netty.File.msg.up.FilePeerUpHeadMsg;
import com.wanlian.netty.File.msg.up.FilePeerUpMsg;
import com.wanlian.netty.model.Address;
import com.wanlian.netty.model.DHTable;
import com.wanlian.netty.model.NetConnection;
import com.wanlian.netty.msg.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

/**
 * Hello world Server.
 *
 * @author holl7
 */
public class ClientSocket {
    private Logger logger = LoggerFactory.getLogger(getClass());

    private boolean BRegister;
    private DHTable dhTable;
    public ServerSocketChannel socketChannel;
    private SocketChannel snChannel;
    private ServerBootstrap bootstrap;
    private NetConnection holeNetConnection;
    private String pubkey;

    /**
     * 启动监听接口
     *
     * @throws Exception
     */
    public void startListen(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // try {
        bootstrap = new ServerBootstrap(); // (2)
        bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)
                .option(ChannelOption.SO_BACKLOG, 1024).option(ChannelOption.SO_REUSEADDR, true)
                .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline p = ch.pipeline();
                        p.addLast(new PeerMsgEncoder());
                        p.addLast(new PeerMsgDecoder());
                        p.addLast(new DeviceClientHandler());
                    }
                });

        logger.info("client bind port :" + port);
        ChannelFuture f = bootstrap.bind(port).sync();
        if (f.isSuccess()) {
            socketChannel = (ServerSocketChannel) f.channel();
        }
    }

    public void holeSomeOne(Address address, HoleBody holeBody) throws Exception {
        HoleMsg holeMsg = new HoleMsg(holeBody);
        NetConnection netConnection = this.connectSomeOne(address);
        if (netConnection.isBresult()) {
            this.sendData(netConnection.getSocketChannel(), holeMsg);
            logger.info("a local port is :" + netConnection.getSocketChannel().localAddress().getPort());
            this.setHoleNetConnection(netConnection);
        }

    }

    /**
     * connect
     *
     * @throws InterruptedException
     */
    public NetConnection connectSomeOne(Address address) {
        NetConnection netConnection = new NetConnection();
        try {
            EventLoopGroup group = new NioEventLoopGroup();
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_REUSEADDR, true).handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(new PeerMsgEncoder());
                    p.addLast(new PeerMsgDecoder());
                    p.addLast(new DeviceClientHandler());
                }
            });
            InetSocketAddress addr = new InetSocketAddress(address.getIp(), address.getPort());
            ChannelFuture f = b.connect(addr).sync();
            logger.info("client connect to server host:{}, port:{}", address.getIp(), address.getPort());
            if (f.isSuccess()) {
                SocketChannel socketChannel = (SocketChannel) f.channel();
                netConnection.setSocketChannel(socketChannel);
                netConnection.setBresult(true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return netConnection;
    }

    /**
     * connect
     *
     * @throws InterruptedException
     */
    public boolean connect(Address address) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, false).option(ChannelOption.SO_RCVBUF, 2 * 1024 * 1024).option(ChannelOption.SO_SNDBUF, 200 * 1024)
                .option(ChannelOption.SO_REUSEADDR, true).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new PeerMsgEncoder());
                p.addLast(new PeerMsgDecoder());
                p.addLast(new DeviceClientHandler());
            }
        });
        // Start the client.
        ChannelFuture f = b.connect(address.getIp(), address.getPort()).sync();
        logger.info("client connect to host:{}, port:{}", address.getIp(), address.getPort());
        if (f.isSuccess()) {
            SocketChannel socketChannel = (SocketChannel) f.channel();
            this.snChannel = socketChannel;
            return true;
        }
        return false;
    }


    /**
     * connect
     *
     * @throws InterruptedException
     */
    public boolean connectFileWorkServer(Address address) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, false).option(ChannelOption.SO_RCVBUF, 1024 * 1024).option(ChannelOption.SO_SNDBUF, 200 * 1024)
                .option(ChannelOption.SO_REUSEADDR, true).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new FilePeerMsgDecoder());
                p.addLast(new FilePeerMsgEncoder());
                p.addLast(new DeviceClientFileHandler());
            }
        });
        // Start the client.
        ChannelFuture f = b.connect(address.getIp(), address.getPort()).sync();
        logger.info("client connect to host:{}, port:{}", address.getIp(), address.getPort());
        if (f.isSuccess()) {
            SocketChannel socketChannel = (SocketChannel) f.channel();
            this.snChannel = socketChannel;
            return true;
        }
        return false;
    }


    /**
     * connect
     *
     * @throws InterruptedException
     */
    public NetConnection connectAndRegister(Address address, String pubkey) throws InterruptedException {
        NetConnection netConnection = new NetConnection();
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_REUSEADDR, true).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new PeerMsgEncoder());
                p.addLast(new PeerMsgDecoder());
                p.addLast(new DeviceClientHandler());
            }
        });
        // Start the client.
        ChannelFuture f = b.connect(address.getIp(), address.getPort()).sync();
        logger.info("client connect to host:{}, port:{}", address.getIp(), address.getPort());
        if (f.isSuccess()) {
            SocketChannel socketChannel = (SocketChannel) f.channel();
            BaseMsg registerMsg = new RegisterMsg();
            Address tAddress = new Address();
            tAddress.setPublickey(pubkey);
            registerMsg.setContent(JsonStringUtil.writeValueAsString(tAddress));
            PeerMsg peerMsg = new PeerMsg(registerMsg);
            socketChannel.writeAndFlush(peerMsg);
            netConnection.setBresult(true);
            netConnection.setSocketChannel(socketChannel);
            return netConnection;
        }

        return netConnection;
    }

    /**
     * 决定要发送给那个
     */
    public void sendSNData(BaseMsg msg) {
        msg.setClientId(this.getPubkey());
        PeerMsg peerMsg = new PeerMsg(msg);
        snChannel.writeAndFlush(peerMsg);
        System.out.println("Send millsecond:" + System.currentTimeMillis());
    }


    /**
     * 决定要发送给那个
     */
    public void sendFileHead(NetFileInfo netFileInfo) {
        FilePeerUpHeadMsg filePeerUpHeadMsg = new FilePeerUpHeadMsg();
        filePeerUpHeadMsg.setHead(netFileInfo);
        FilePeerMsg filePeerMsg = new FilePeerMsg(filePeerUpHeadMsg);
        snChannel.writeAndFlush(filePeerMsg);
    }

    /**
     * 决定要发送给那个
     */
    public void sendFileData(NetFileInfo netFileInfo) {
        sendFileHead(netFileInfo);
        FilePeerUpMsg filePeerInfo = new FilePeerUpMsg(netFileInfo);
        FilePeerMsg peerMsg = new FilePeerMsg(filePeerInfo);
        snChannel.writeAndFlush(peerMsg);
//        sendFileHead(netFileInfo);
    }

    public void sendDownHead(NetFileInfo netFileInfo){
        FilePeerDownHeadMsg filePeerDownHeadMsg = new FilePeerDownHeadMsg();
        FileHead fileHead = new FileHead(netFileInfo);
        filePeerDownHeadMsg.setHead(fileHead);
        FilePeerMsg filePeerMsg = new FilePeerMsg(filePeerDownHeadMsg);
        snChannel.writeAndFlush(filePeerMsg);
    }


    public void sendDownFile(NetFileInfo netFileInfo){
        String filepath=String.format("%s/%s","/mnt/mmcblk0p1/photo",netFileInfo.getFileName());
        System.out.println(filepath);
        netFileInfo.setFilePath(filepath);
        FilePeerDownMsg filePeerDownMsg = new FilePeerDownMsg(netFileInfo);
        FilePeerMsg filePeerMsg = new FilePeerMsg(filePeerDownMsg);
        snChannel.writeAndFlush(filePeerMsg);
    }


    /**
     * 决定要发送给那个
     */
    public void sendData(SocketChannel socketChannel, BaseMsg msg) {
        PeerMsg peerMsg = new PeerMsg(msg);
        msg.setClientId(this.getPubkey());
        socketChannel.writeAndFlush(peerMsg);
    }

    /**
     * 向服务器注册
     *
     * @param pubkey
     * @return
     */
    public void registerSnServer(String pubkey) {
        BaseMsg registerMsg = new RegisterMsg();
        registerMsg.setClientId(pubkey);
        Address address = new Address();
        address.setPublickey(pubkey);
        registerMsg.setContent(JsonStringUtil.writeValueAsString(address));
        this.sendData(snChannel, registerMsg);
    }

    /**
     * 查询路由表
     */
    public void querySNDHTable() {
        BaseMsg baseMsg = new DHTMsg();
        this.sendData(snChannel, baseMsg);
    }

    /**
     * 保持连接
     *
     * @throws InterruptedException
     */
    public void keepConnect() throws InterruptedException {
        BaseMsg baseMsg = new PingMsg();
        baseMsg.setClientId(this.pubkey);
        baseMsg.setTagClientId("d5c0bfaca2934f22bc3f8ff4b7b0dc97");
        //baseMsg.setContent(baseMsg.toString());
        this.sendData(snChannel, baseMsg);
    }

    /**
     * 回复给服务器已经准备好了
     *
     * @param holeBody
     */
    public void sendHoleReady(HoleBody holeBody) {
        BaseMsg baseMsg = new BaseMsg();
        baseMsg.setType(MsgType.HOLE_READY);
        baseMsg.setContent(JsonStringUtil.writeValueAsString(holeBody));
        this.sendData(snChannel, baseMsg);
    }

    public void closeHoleConnection() {
        if (this.getHoleNetConnection() != null) {
            this.getHoleNetConnection().getSocketChannel().close();
        }
    }

    public boolean isBRegister() {
        return BRegister;
    }

    public void setBRegister(boolean bRegister) {
        BRegister = bRegister;
    }

    public DHTable getDhTable() {
        return dhTable;
    }

    public void setDhTable(DHTable dhTable) {
        this.dhTable = dhTable;
        dhTable.getIptables().remove(this.pubkey);
    }

    public String getPubkey() {
        return pubkey;
    }

    public void setPubkey(String pubkey) {

        this.pubkey = pubkey;
    }

    public NetConnection getHoleNetConnection() {

        return holeNetConnection;
    }

    public void setHoleNetConnection(NetConnection holeNetConnection) {
        this.holeNetConnection = holeNetConnection;
    }

}
